
from ctypes import sizeof
from PyQt5.QtWidgets import QApplication
from PyQt5.QtNetwork import QTcpSocket
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import QThread
from PyQt5.QtCore import QObject
from PyQt5.QtCore import QObject
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QTextImageFormat
from PyQt5.QtGui import QTextCursor
import json
import sys
import os
import time

from GUIFrame import MyFrame

IP = '8.130.106.82'
PORT = 12345

PATCH_SIZE = 4096  # 4KB
MAX_FILE_LENGTH = 1024 * 1024 * 10 # 10MB
LISTEN_INTERVAL = 500

OTHER_ERR = -1
SOCKET_ERR = 0
TEXT_INVALID = 1
IMAGE_INVALID = 2
TEXT_SENT = 3
IMAGE_SENT = 4
TEXT_RECV = 5
IMAGE_RECV = 6
IMAGE_DATA  =7


class SocketWorker(QObject):
    socketStateSignal = pyqtSignal(int)

    textMessageSentSignal = pyqtSignal(int)          # 表示消息发送结果的信号
    imageMessageSentSignal = pyqtSignal(int)

    textMessageReceivedSignal = pyqtSignal(str)      # 传送接收到的消息内容的信号
    imageMessageReceivedSignal = pyqtSignal(bytes)
    # pyqtSignal 必须为类属性时才能使用类方法 connect

    def __init__(self, ip, port, tcpSocket):
        super().__init__()
        self.timer = QTimer()
        self.timer.timeout.connect(self.listenOnce)
        self.timer.start(LISTEN_INTERVAL)
        if tcpSocket is None:
            self.tcpSocket = QTcpSocket()
        else:
            self.tcpSocket = tcpSocket
        self.tryConnectToServer()

    def tryConnectToServer(self):
        print("TCP disconnected. Retrying...")
        try:
            self.tcpSocket.connectToHost(IP, PORT)
            # self.tcpSocket.waitForConnected(1000)
        except Exception as e:
            print(e)
        else:
            print("TCP reconnected successfully")
        finally:
            return


    @pyqtSlot(str)
    def sendTextMessage(self, text:str):
        if not self.tcpSocket or self.tcpSocket.state() == QTcpSocket.UnconnectedState:
            print(f"Abnormal socket state: {self.tcpSocket.state()}")
            self.textMessageSentSignal.emit(SOCKET_ERR)
            self.tryConnectToServer()
        if len(text) > PATCH_SIZE:
            self.textMessageSentSignal.emit(TEXT_INVALID)
            return
        data = json.dumps({'type':'text', 'data':text})
        self.tcpSocket.write(data.encode())
        # self.tcpSocket.waitForBytesWritten(1000)
        self.textMessageSentSignal.emit(TEXT_SENT)


    @pyqtSlot(str)
    def sendImageMessage(self, imagePath:str):
        """
            发送一张图片分为两步
            1. 发送消息类型: image, 图片大小
            2. 发送图片的二进制数据
        """
        if not self.tcpSocket or self.tcpSocket.state() == QTcpSocket.UnconnectedState:
            print(f"Abnormal socket state: {self.tcpSocket.state()}")
            self.tryConnectToServer(self)
        # 发送的图片不能超过10MB
        fileSize = os.path.getsize(imagePath)
        if fileSize >= MAX_FILE_LENGTH:
            self.imageMessageSentSignal.emit(IMAGE_INVALID)
            return
        
        data = json.dumps({'type':'image', 'imageSize': fileSize})
        self.tcpSocket.write(data.encode())
        # self.tcpSocket.waitForBytesWritten(1000)
        try:
            with open(imagePath, 'rb') as f: # rb: readonly, binary
                data = f.read(MAX_FILE_LENGTH)
                self.tcpSocket.write(data)
                # self.tcpSocket.waitForBytesWritten(2000)
        except Exception as e:
            print(e)
            self.imageMessageSentSignal.emit(OTHER_ERR)
            return
        else:
            self.imageMessageSentSignal.emit(IMAGE_SENT)
            

    def listenOnce(self):
        self.timer.stop()
        self.timer.start(LISTEN_INTERVAL)
        if not self.tcpSocket or self.tcpSocket.state() == QTcpSocket.UnconnectedState:
            print(f"Abnormal socket state: {self.tcpSocket.state()}")
            self.tryConnectToServer()
            
        data = self.tcpSocket.read(PATCH_SIZE)
        if data is None or len(data) == 0:
            return
        try:
            jsonData = json.loads(data)
        except json.JSONDecodeError:
            print("JSON decode error, skip this message'")
            return
        if jsonData['type'] == 'text':
            data = jsonData['data']
            self.textMessageReceived.emit(data)
        elif jsonData['type'] == 'image':
            imageSize = jsonData['imageSize']
            if imageSize > MAX_FILE_LENGTH:
                print("File size too large, skip this message")
                return
            data = self.tcpSocket.read(imageSize)
            self.imageMessageReceivedSignal.emit(data)
        else:
            print("Unknown message type, skip this message")


    
class ClientGUI(MyFrame):
    # 此信号在主线程中发送, 触发子线程的发送方法
    sendMessageWorkSignal = pyqtSignal(str) 
    sendImageWorkSignal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        
        self.socketWorker = None
        self.socketThread = None
        
        self.createFunctions()
        self.configNetwork()
        self.switchState(False)
        

    def switchState(self, connected:bool):
        if connected:
            self.QPushButton_applyServerIP.setEnabled(False)
            self.QPushButton_applyServerPort.setEnabled(False)
            self.QPushButton_connect.setEnabled(False)
            self.QPushButton_disconnect.setEnabled(True)
            self.QPushButton_send.setEnabled(True)
        else:
            self.QPushButton_applyServerIP.setEnabled(True)
            self.QPushButton_applyServerPort.setEnabled(True)
            self.QPushButton_connect.setEnabled(True)
            self.QPushButton_disconnect.setEnabled(False)
            self.QPushButton_send.setEnabled(False)
            

    def createFunctions(self):
        self.QLabel_userName.mousePressEvent = self.toggleEditUserName
        self.QLineEdit_userName.returnPressed.connect(self.updateUserName)

        self.QPushButton_applyServerIP.clicked.connect(self.updateServerIP)
        self.QPushButton_applyServerPort.clicked.connect(self.updateServerPort)
        self.QPushButton_send.clicked.connect(self.sendTextMessage)
        self.QPushButton_clear.clicked.connect(self.clearMessage)
        self.QPushButton_connect.clicked.connect(self.connectToServer)
        self.QPushButton_disconnect.clicked.connect(self.disconnectFromServer)


    def toggleEditUserName(self, event):
        # 点击用户名标签时切换为编辑模式
        self.QLabel_userName.setHidden(True)
        self.QLineEdit_userName.setHidden(False)
        self.QLineEdit_userName.setText(self.QLabel_userName.text())
        self.QLineEdit_userName.setFocus()


    def updateUserName(self)->None:
        self.userName = self.QLineEdit_userName.text()
        self.QLabel_userName.setText(self.userName)
        self.QLabel_userName.setHidden(False)
        self.QLineEdit_userName.setHidden(True)


    def updateServerIP(self)->None:
        self.ip = self.QLineEdit_serverIP.text()


    def updateServerPort(self)->None:
        self.port = self.QLineEdit_serverPort.text()


    def configNetwork(self):
        pass


    def connectToServer(self):
        # self.tcpSocket = QTcpSocket()
        # try:
        #     self.tcpSocket.connectToHost(IP,PORT)
        # except Exception as e:
        #     print(e)
        #     return

        self.socketThread = QThread(self)
        # self.socketWorker = SocketWorker(self.ip, self.port, self.tcpSocket)
        self.socketWorker = SocketWorker(self.ip, self.port, tcpSocket=None)
        self.socketWorker.moveToThread(self.socketThread)

        self.sendMessageWorkSignal.connect(self.socketWorker.sendTextMessage)
        self.sendImageWorkSignal.connect(self.socketWorker.sendImageMessage)

        self.socketWorker.textMessageSentSignal.connect(self.addTextToTextEdit)
        self.socketWorker.imageMessageSentSignal.connect(self.addImageToTextEdit)

        self.socketWorker.textMessageReceivedSignal.connect(self.addTextToTextEdit)
        self.socketWorker.imageMessageReceivedSignal.connect(self.addImageToTextEdit)

        self.socketWorker.socketStateSignal.connect(self.switchState)

        self.socketThread.start()
        print('Worker thread initialized successfully')



        self.switchState(True)


    def disconnectFromServer(self):
        self.socketThread.quit()
        self.socketThread.wait()
        self.socketWorker = None
        self.socketThread = None
        
        self.switchState(False)
    
    
    def updateMemebers(self):
        pass
    

    def sendTextMessage(self):
        text = self.QTextEdit_inputBox.toPlainText()
        if text == '':
            return
        self.sendMessageWorkSignal.emit(text)
        

        
    # TODO
    def sendFile(self):
        pass
    
    def sendPictureMessage(self):
        pass


    def addImageToTextEdit(self):
        cursor = self.QTextEdit_chatBox.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText('\n')
        cursor.movePosition(QTextCursor.End)
        image_format = QTextImageFormat()
        image_format.setWidth(100)
        image_format.setHeight(100)
        # image_format.setName(image_path)
        cursor.insertImage(image_format)


    def clearMessage(self):
        self.QTextEdit_chatBox.clear()
        self.QTextEdit_inputBox.clear()


    def addTextToTextEdit(self, info:int):
        if info == TEXT_INVALID:
            text = '<font color="red">Message too long, failed to send!</font>'
        elif info == SOCKET_ERR:
            text = '<font color="red">Socket error, failed to send!</font>'
        elif info == TEXT_SENT:
            text = self.QTextEdit_inputBox.toPlainText()
            text = '<font color="blue">'+text+'</font>'
            self.QTextEdit_inputBox.clear()
        self.QTextEdit_chatBox.insertHtml(text)


# Socket client running on Windows
if __name__ == '__main__':
    app = QApplication(sys.argv)
    font = app.font()
    font.setFamily('Microsoft YaHei')
    app.setFont(font)

    mainWindow = ClientGUI()

    mainWindow.show()
    app.exec_()
